import Cocoa
import Foundation

// 通知扩展
extension Notification.Name {
    static let windowsDidUpdate = Notification.Name("windowsDidUpdate")
}

// 表示一个应用程序窗口的数据结构
struct WindowInfo {
    let windowId: CGWindowID
    let processId: pid_t
    let appName: String
    let windowTitle: String
    let appIconPath: String?
    let isMinimized: Bool
    let bounds: CGRect
    let screenNumber: Int
    
    var displayName: String {
        // 根据应用类型优化显示名称
        switch appName.lowercased() {
        case "visual studio code", "code":
            // VSCode显示项目名
            let components = windowTitle.components(separatedBy: " — ")
            return components.first ?? windowTitle
        case "wechat", "微信":
            // 微信根据窗口标题区分
            if windowTitle.contains("朋友圈") || windowTitle.contains("Moments") {
                return "朋友圈"
            }
            return "微信"
        case "google chrome", "chrome":
            // Chrome显示页面标题（去掉" - Google Chrome"后缀）
            return windowTitle.replacingOccurrences(of: " - Google Chrome", with: "")
        default:
            return windowTitle.isEmpty ? appName : windowTitle
        }
    }
}

// 窗口管理器类
class WindowManager: ObservableObject {
    static let shared = WindowManager()
    @Published var windows: [WindowInfo] = []
    private var observers: [AXObserver] = []
    private var runningApplications: [NSRunningApplication] = []
    private var fallbackTimer: Timer?
    
    init() {
        startMonitoring()
    }
    
    private func startMonitoring() {
        // 监听应用启动和退出
        NSWorkspace.shared.notificationCenter.addObserver(
            self,
            selector: #selector(applicationDidLaunch(_:)),
            name: NSWorkspace.didLaunchApplicationNotification,
            object: nil
        )
        
        NSWorkspace.shared.notificationCenter.addObserver(
            self,
            selector: #selector(applicationDidTerminate(_:)),
            name: NSWorkspace.didTerminateApplicationNotification,
            object: nil
        )
        // 其它系统级事件：激活/隐藏/显示/切换空间/唤醒
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.didActivateApplicationNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in self?.refreshWindows() }
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.didDeactivateApplicationNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in self?.refreshWindows() }
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.didHideApplicationNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in self?.refreshWindows() }
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.didUnhideApplicationNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in self?.refreshWindows() }
        
        // 初始加载当前运行的应用
        refreshWindows()

        // 监听 Space 切换，及时刷新（展示所有桌面应用）
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.activeSpaceDidChangeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.refreshWindows()
        }

        // 低频后备刷新：偶发应用不发 AX 事件时，保证 1s 内更新
        fallbackTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.refreshWindows()
        }
    }
    
    @objc private func applicationDidLaunch(_ notification: Notification) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.refreshWindows()
        }
    }
    
    @objc private func applicationDidTerminate(_ notification: Notification) {
        refreshWindows()
    }
    
    func refreshWindows() {
        // print("🔄 开始刷新窗口列表...")
        var newWindows: [WindowInfo] = []
        let screens = NSScreen.screens

        // 先通过 CGWindow 列举所有窗口（包含其它 Space）以获得窗口列表和 bounds
        var cgWindowMap: [String: [String: Any]] = [:] // key: "pid:title" -> info
        if let cgList = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[String: Any]] {
            for info in cgList {
                guard let layer = info[kCGWindowLayer as String] as? Int, layer == 0 else { continue }
                guard let pid = info[kCGWindowOwnerPID as String] as? pid_t else { continue }
                let title = info[kCGWindowName as String] as? String ?? ""
                // 构造键
                let key = "\(pid):\(title)"
                cgWindowMap[key] = info
            }
        }

        // 枚举运行应用，优先使用 AX 信息，回退到 CG 窗口信息
        let runningApps = NSWorkspace.shared.runningApplications
        for app in runningApps {
            if app.bundleIdentifier == Bundle.main.bundleIdentifier { continue }
            // 只处理真实窗口应用
            if app.activationPolicy != .regular { continue }
            
            // 额外检查：跳过纯后台应用
            if isBackgroundOnlyApp(app) { continue }

            let pid = app.processIdentifier
            let appElement = AXUIElementCreateApplication(pid)
            var axWindowsRef: CFTypeRef?
            AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &axWindowsRef)
            let axWindows = (axWindowsRef as? [AXUIElement]) ?? []

            if axWindows.count > 0 {
                var axTitles: Set<String> = []
                for axWindow in axWindows {
                    // 只处理角色为窗口的元素
                    var roleRef: CFTypeRef?
                    if AXUIElementCopyAttributeValue(axWindow, kAXRoleAttribute as CFString, &roleRef) == .success,
                       let role = roleRef as? String, role != kAXWindowRole as String {
                        continue
                    }

                    // 标题
                    var title = ""
                    var titleRef: CFTypeRef?
                    if AXUIElementCopyAttributeValue(axWindow, kAXTitleAttribute as CFString, &titleRef) == .success,
                       let t = titleRef as? String {
                        title = t
                    }
                    axTitles.insert(title)

                    // 最小化
                    var minimized = false
                    var miniRef: CFTypeRef?
                    if AXUIElementCopyAttributeValue(axWindow, kAXMinimizedAttribute as CFString, &miniRef) == .success,
                       let m = miniRef as? Bool {
                        minimized = m
                    }

                    // 可见性：使用 kAXHiddenAttribute 反推
                    var visible = true
                    var hiddenRef: CFTypeRef?
                    if AXUIElementCopyAttributeValue(axWindow, kAXHiddenAttribute as CFString, &hiddenRef) == .success,
                       let h = hiddenRef as? Bool {
                        visible = !h
                    }

                    // 位置和大小
                    var position = CGPoint.zero
                    var size = CGSize.zero
                    var posRef: CFTypeRef?
                    var sizeRef: CFTypeRef?
                    if AXUIElementCopyAttributeValue(axWindow, kAXPositionAttribute as CFString, &posRef) == .success,
                       let p = posRef {
                        var cgPoint = CGPoint.zero
                        if AXValueGetType(p as! AXValue) == .cgPoint {
                            AXValueGetValue(p as! AXValue, .cgPoint, &cgPoint)
                            position = cgPoint
                        }
                    }
                    if AXUIElementCopyAttributeValue(axWindow, kAXSizeAttribute as CFString, &sizeRef) == .success,
                       let s = sizeRef {
                        var cgSize = CGSize.zero
                        if AXValueGetType(s as! AXValue) == .cgSize {
                            AXValueGetValue(s as! AXValue, .cgSize, &cgSize)
                            size = cgSize
                        }
                    }

                    if size.width < 50 || size.height < 30 { continue }

                    // Normalize AX (top-left origin) to global bottom-left using main screen as reference
                    // 先用全局换算得到一个初始 rect
                    var windowRect = rectTopLeftToBottomLeft(originTopLeft: position, size: size)
                    // 尝试按各屏 topY 做更精准的 TL->BL 转换，挑选匹配度更高的结果
                    var bestRect = windowRect
                    var bestArea: CGFloat = -1
                    for screen in screens {
                        let topY = screen.frame.maxY
                        let candidate = rectTopLeftToBottomLeft(originTopLeft: position, size: size, topY: topY)
                        let inter = candidate.intersection(screen.frame)
                        let area = max(0, inter.width) * max(0, inter.height)
                        if area > bestArea {
                            bestArea = area
                            bestRect = candidate
                        }
                    }
                    windowRect = bestRect

                    // 屏幕归属：先用相交面积最大；若都为0，则用中心点到各屏幕的最短距离
                    var screenNumber = 0
                    var areas: [CGFloat] = []
                    for screen in screens {
                        let inter = windowRect.intersection(screen.frame)
                        let area = max(0, inter.width) * max(0, inter.height)
                        areas.append(area)
                    }
                    if let maxArea = areas.max(), maxArea > 0,
                       let idx = areas.firstIndex(of: maxArea) {
                        screenNumber = idx
                    } else {
                        // 无相交：计算窗口中心到每个屏幕 frame 的距离，取最近
                        let center = CGPoint(x: windowRect.midX, y: windowRect.midY)
                        var bestDist = CGFloat.greatestFiniteMagnitude
                        for (idx, screen) in screens.enumerated() {
                            let dist = distanceFromPointToRect(center, screen.frame)
                            if dist < bestDist {
                                bestDist = dist
                                screenNumber = idx
                            }
                        }
                    }

                    let appName = app.localizedName ?? "Unknown"
                    var displayTitle = title.isEmpty ? appName : title

                    // 针对微信，尝试从子元素中查找“朋友圈”关键词
                    if appName.lowercased().contains("wechat") || appName.contains("微信") {
                        if displayTitle.isEmpty {
                            if let found = findWechatSubTitle(axWindow) {
                                displayTitle = found
                            } else {
                                displayTitle = "微信"
                            }
                        } else {
                            // 标题存在，但尝试识别朋友圈
                            if displayTitle.contains("朋友圈") || displayTitle.contains("Moments") {
                                displayTitle = "朋友圈"
                            }
                        }
                    }

                    // 保证即使窗口不在当前激活 Space（即 AX 返回的窗口可能不可见），
                    // 也能被列入任务栏。只在窗口被最小化时跳过。
                    if minimized { continue }

                    let win = WindowInfo(
                        windowId: 0,
                        processId: pid,
                        appName: appName,
                        windowTitle: displayTitle,
                        appIconPath: getAppIconPath(for: app),
                        isMinimized: minimized,
                        bounds: windowRect,
                        screenNumber: screenNumber
                    )
                    newWindows.append(win)
                }
                // 合并同一应用在其它 Space 的 CG 窗口（AX 未返回的）
                for (_, info) in cgWindowMap {
                    guard let pidVal = info[kCGWindowOwnerPID as String] as? pid_t, pidVal == pid else { continue }
                    let title = (info[kCGWindowName as String] as? String) ?? ""
                    if axTitles.contains(title) { continue }
                    guard let bounds = info[kCGWindowBounds as String] as? [String: Any],
                          let x = bounds["X"] as? CGFloat,
                          let y = bounds["Y"] as? CGFloat,
                          let w = bounds["Width"] as? CGFloat,
                          let h = bounds["Height"] as? CGFloat else { continue }
                    if w < 50 || h < 30 { continue }
                    let windowRect = CGRect(x: x, y: y, width: w, height: h)

                    // 归属屏幕
                    var screenNumber = 0
                    var areas: [CGFloat] = []
                    for screen in screens {
                        let inter = windowRect.intersection(screen.frame)
                        let area = max(0, inter.width) * max(0, inter.height)
                        areas.append(area)
                    }
                    if let maxArea = areas.max(), maxArea > 0,
                       let idx = areas.firstIndex(of: maxArea) {
                        screenNumber = idx
                    } else {
                        let center = CGPoint(x: windowRect.midX, y: windowRect.midY)
                        var bestDist = CGFloat.greatestFiniteMagnitude
                        for (idx, screen) in screens.enumerated() {
                            let dist = distanceFromPointToRect(center, screen.frame)
                            if dist < bestDist {
                                bestDist = dist
                                screenNumber = idx
                            }
                        }
                    }

                    let appName = app.localizedName ?? "Unknown"
                    let displayTitle = title.isEmpty ? appName : title

                    let win = WindowInfo(
                        windowId: 0,
                        processId: pid,
                        appName: appName,
                        windowTitle: displayTitle,
                        appIconPath: getAppIconPath(for: app),
                        isMinimized: false,
                        bounds: windowRect,
                        screenNumber: screenNumber
                    )
                    newWindows.append(win)
                }
            } else {
                // 没有 AX 窗口信息时退回到 CGWindow
                for (_, info) in cgWindowMap {
                    guard let pidVal = info[kCGWindowOwnerPID as String] as? pid_t, pidVal == pid else { continue }
                    guard let bounds = info[kCGWindowBounds as String] as? [String: Any],
                          let x = bounds["X"] as? CGFloat,
                          let y = bounds["Y"] as? CGFloat,
                          let w = bounds["Width"] as? CGFloat,
                          let h = bounds["Height"] as? CGFloat else { continue }

                    if w < 50 || h < 30 { continue }
                    let windowRect = CGRect(x: x, y: y, width: w, height: h)

                    var screenNumber = 0
                    var areas: [CGFloat] = []
                    for screen in screens {
                        let inter = windowRect.intersection(screen.frame)
                        let area = max(0, inter.width) * max(0, inter.height)
                        areas.append(area)
                    }
                    if let maxArea = areas.max(), maxArea > 0,
                       let idx = areas.firstIndex(of: maxArea) {
                        screenNumber = idx
                    } else {
                        let center = CGPoint(x: windowRect.midX, y: windowRect.midY)
                        var bestDist = CGFloat.greatestFiniteMagnitude
                        for (idx, screen) in screens.enumerated() {
                            let dist = distanceFromPointToRect(center, screen.frame)
                            if dist < bestDist {
                                bestDist = dist
                                screenNumber = idx
                            }
                        }
                    }


                    let appName = app.localizedName ?? "Unknown"
                    let title = info[kCGWindowName as String] as? String ?? appName

                    let win = WindowInfo(
                        windowId: 0,
                        processId: pid,
                        appName: appName,
                        windowTitle: title,
                        appIconPath: getAppIconPath(for: app),
                        isMinimized: false,
                        bounds: windowRect,
                        screenNumber: screenNumber
                    )
                    newWindows.append(win)
                }
            }
        }

        // 合并去重：以 (processId, windowTitle, screenNumber, size) 为键，避免跨 Space 同标题被误并
        var unique: [String: WindowInfo] = [:]
        for w in newWindows {
            let key = "\(w.processId):\(w.windowTitle):\(w.screenNumber):\(Int(w.bounds.width))x\(Int(w.bounds.height))"
            if let exist = unique[key] {
                // 保留较大尺寸或非最小化的窗口
                if !exist.isMinimized && w.isMinimized { continue }
                unique[key] = w
            } else {
                unique[key] = w
            }
        }

        var final = Array(unique.values).sorted { (a, b) -> Bool in
            if a.screenNumber != b.screenNumber { return a.screenNumber < b.screenNumber }
            if a.appName != b.appName { return a.appName < b.appName }
            return a.windowTitle < b.windowTitle
        }
        // 如果同一进程存在带具体窗口标题的条目，则移除仅显示应用名或空标题的占位条目
        // 改为按应用名分组（忽略大小写），处理多进程应用（例如 Chrome）
        var byApp: [String: [WindowInfo]] = [:]
        for w in final {
            let key = w.appName.lowercased()
            byApp[key, default: []].append(w)
        }

        var filtered: [WindowInfo] = []
        for (_, group) in byApp {
            // 是否存在至少一个带有非应用名、非空标题的窗口
            let hasTitled = group.contains { !$0.windowTitle.isEmpty && $0.windowTitle != $0.appName }
            if hasTitled {
                // 保留有具体标题的窗口，去掉只有应用名或空标题的占位项
                filtered.append(contentsOf: group.filter { !$0.windowTitle.isEmpty && $0.windowTitle != $0.appName })
            } else {
                filtered.append(contentsOf: group)
            }
        }

        final = filtered.sorted { (a, b) -> Bool in
            if a.screenNumber != b.screenNumber { return a.screenNumber < b.screenNumber }
            if a.appName != b.appName { return a.appName < b.appName }
            return a.windowTitle < b.windowTitle
        }
        DispatchQueue.main.async {
            self.windows = final
            // print("✅ 刷新完成，共找到 \(final.count) 个窗口")
            
            // 每次窗口刷新后都检查是否需要调整窗口
            NotificationCenter.default.post(name: .windowsDidUpdate, object: self.windows)
        }
    }
    
    private func isBackgroundOnlyApp(_ app: NSRunningApplication) -> Bool {
        // 检查是否是纯后台应用
        if app.activationPolicy == .accessory || app.activationPolicy == .prohibited {
            return true
        }
        
        // 优先使用 CGWindow 列表来判断是否存在该进程的窗口（包含其它 Space）
        let pid = app.processIdentifier
        if let cgList = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[String: Any]] {
            for info in cgList {
                if let pidVal = info[kCGWindowOwnerPID as String] as? pid_t, pidVal == pid {
                    // 只关心图层为0的普通窗口，并且尺寸合理
                    if let layer = info[kCGWindowLayer as String] as? Int, layer != 0 { continue }
                    if let bounds = info[kCGWindowBounds as String] as? [String: Any],
                       let w = bounds["Width"] as? CGFloat, let h = bounds["Height"] as? CGFloat {
                        if w >= 50 && h >= 30 { return false }
                    } else {
                        // 若无法读取 bounds，但存在窗口条目，则认为有窗口
                        return false
                    }
                }
            }
        }

        // 退回到 Accessibility 判断：若 AX 没有窗口或所有窗口都最小化/隐藏，则视为后台应用
        let appElement = AXUIElementCreateApplication(app.processIdentifier)
        var windowsRef: CFTypeRef?
        if AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsRef) == .success,
           let windows = windowsRef as? [AXUIElement] {
            if windows.isEmpty { return true }

            for window in windows {
                var miniRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXMinimizedAttribute as CFString, &miniRef) == .success,
                   let minimized = miniRef as? Bool, minimized { continue }

                var hiddenRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXHiddenAttribute as CFString, &hiddenRef) == .success,
                   let hidden = hiddenRef as? Bool, hidden { continue }

                // 存在至少一个未最小化且未隐藏的窗口
                return false
            }
            return true
        }

        return true
    }

    // 尝试从微信窗口的子元素中识别更具体的标题（例如“朋友圈”）
    private func findWechatSubTitle(_ window: AXUIElement) -> String? {
        var childrenRef: CFTypeRef?
        if AXUIElementCopyAttributeValue(window, kAXChildrenAttribute as CFString, &childrenRef) != .success { return nil }
        guard let children = childrenRef as? [AXUIElement] else { return nil }

        for child in children {
            var descRef: CFTypeRef?
            if AXUIElementCopyAttributeValue(child, kAXDescriptionAttribute as CFString, &descRef) == .success,
               let desc = descRef as? String {
                if desc.contains("朋友圈") || desc.contains("Moments") {
                    return "朋友圈"
                }
            }

            var titleRef: CFTypeRef?
            if AXUIElementCopyAttributeValue(child, kAXTitleAttribute as CFString, &titleRef) == .success,
               let t = titleRef as? String {
                if t.contains("朋友圈") || t.contains("Moments") {
                    return "朋友圈"
                }
            }
            // 递归检查子元素
            if let sub = findWechatSubTitle(child) { return sub }
        }

        return nil
    }
    
    private func checkIfWindowMinimized(windowId: CGWindowID, processId: pid_t) -> Bool {
        // 使用Accessibility API检查窗口是否最小化
        let app = AXUIElementCreateApplication(processId)
        var windowsRef: CFTypeRef?
        
        if AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &windowsRef) == .success,
           let windows = windowsRef as? [AXUIElement] {
            
            for window in windows {
                var minimizedRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXMinimizedAttribute as CFString, &minimizedRef) == .success,
                   let minimized = minimizedRef as? Bool {
                    return minimized
                }
            }
        }
        
        return false
    }
    
    private func getAppIconPath(for app: NSRunningApplication) -> String? {
        // 返回 .app 包路径，后续用 NSWorkspace.icon(forFile:) 取图标
        return app.bundleURL?.path
    }
    
    // 激活指定窗口
    func activateWindow(_ windowInfo: WindowInfo) {
        let app = AXUIElementCreateApplication(windowInfo.processId)
        var windowsRef: CFTypeRef?
        
        if AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &windowsRef) == .success,
           let windows = windowsRef as? [AXUIElement] {
            
            for window in windows {
                // 检查是否是目标窗口（通过标题匹配）
                var titleRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &titleRef) == .success,
                   let title = titleRef as? String,
                   title == windowInfo.windowTitle {
                    
                    // 如果窗口最小化，先恢复
                    if windowInfo.isMinimized {
                        AXUIElementSetAttributeValue(window, kAXMinimizedAttribute as CFString, false as CFBoolean)
                    }
                    
                    // 激活窗口
                    AXUIElementPerformAction(window, kAXRaiseAction as CFString)
                    
                    // 激活应用
                    if let runningApp = NSRunningApplication(processIdentifier: windowInfo.processId) {
                        runningApp.activate(options: [.activateIgnoringOtherApps])
                    }
                    
                    break
                }
            }
        }
    }
    
    // 最小化指定窗口
    func minimizeWindow(_ windowInfo: WindowInfo) {
        let app = AXUIElementCreateApplication(windowInfo.processId)
        var windowsRef: CFTypeRef?
        
        if AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &windowsRef) == .success,
           let windows = windowsRef as? [AXUIElement] {
            
            for window in windows {
                var titleRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &titleRef) == .success,
                   let title = titleRef as? String,
                   title == windowInfo.windowTitle {
                    
                    AXUIElementSetAttributeValue(window, kAXMinimizedAttribute as CFString, true as CFBoolean)
                    break
                }
            }
        }
    }
    
    // 切换窗口最小化状态
    func toggleWindowMinimization(_ windowInfo: WindowInfo) {
        // 重新获取窗口的最小化状态并切换
        let app = AXUIElementCreateApplication(windowInfo.processId)
        var windowsRef: CFTypeRef?

        if AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &windowsRef) == .success,
           let windows = windowsRef as? [AXUIElement] {

            for window in windows {
                var titleRef: CFTypeRef?
                if AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &titleRef) == .success,
                   let title = titleRef as? String,
                   title == windowInfo.windowTitle {

                    var miniRef: CFTypeRef?
                    var currentMin = false
                    if AXUIElementCopyAttributeValue(window, kAXMinimizedAttribute as CFString, &miniRef) == .success,
                       let m = miniRef as? Bool {
                        currentMin = m
                    }

                    // 切换状态
                    AXUIElementSetAttributeValue(window, kAXMinimizedAttribute as CFString, (!currentMin) as CFBoolean)
                    // 更新缓存
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
                        self.refreshWindows()
                    }
                    break
                }
            }
        }
    }
}

// 计算点到矩形的欧氏距离（在矩形内则为0）
private func distanceFromPointToRect(_ p: CGPoint, _ r: CGRect) -> CGFloat {
    let dx: CGFloat
    if p.x < r.minX { dx = r.minX - p.x }
    else if p.x > r.maxX { dx = p.x - r.maxX }
    else { dx = 0 }

    let dy: CGFloat
    if p.y < r.minY { dy = r.minY - p.y }
    else if p.y > r.maxY { dy = p.y - r.maxY }
    else { dy = 0 }

    return sqrt(dx*dx + dy*dy)
}